package fiuba.algo3.tp2.modelo.guardadores;

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import fiuba.algo3.tp2.modelo.centrales.CentralElectrica;
import fiuba.algo3.tp2.modelo.centrales.CentralEolica;
import fiuba.algo3.tp2.modelo.centrales.CentralMineral;
import fiuba.algo3.tp2.modelo.centrales.CentralNuclear;
import fiuba.algo3.tp2.modelo.estaciones.EstacionDeBomberos;
import fiuba.algo3.tp2.modelo.interfaces.ConectableATuberia;
import fiuba.algo3.tp2.modelo.juego.Juego;
import fiuba.algo3.tp2.modelo.juego.Jugador;
import fiuba.algo3.tp2.modelo.lineasTension.LineaDeTension;
import fiuba.algo3.tp2.modelo.mapa.Mapa;
import fiuba.algo3.tp2.modelo.objetos.ObjetoConVida;
import fiuba.algo3.tp2.modelo.posicion.Posicion;
import fiuba.algo3.tp2.modelo.pozos.PozoDeAgua;
import fiuba.algo3.tp2.modelo.rutas.RutaPavimentada;
import fiuba.algo3.tp2.modelo.terrenos.TerrenoDeAgua;
import fiuba.algo3.tp2.modelo.terrenos.TerrenoLlano;
import fiuba.algo3.tp2.modelo.tuberias.TuberiaDeAgua;
import fiuba.algo3.tp2.modelo.zonas.Comercial;
import fiuba.algo3.tp2.modelo.zonas.Industrial;
import fiuba.algo3.tp2.modelo.zonas.Residencial;
import fiuba.algo3.tp2.modelo.zonas.Zona;

/*********** Persistencia************
 * 
 * Serializa un juego, pasa a formato XML, y deserializa un XML recuperando el juego
 * Por cada objeto se tiene un serializar y un deserializar
 * */

public class GuardadorXML {
	
	private String nombreArchivo;

	
	public GuardadorXML(String nombre) {
		this.nombreArchivo = nombre; // "AlgoCity_partidas.xml";
	}
	
	
	//SERIALIZAR*********************************************************
	private Element serializarPosicion(Document doc,Posicion posicion){
		
		Element element = doc.createElement("posicion");
		element.setAttribute("posEnX",Integer.toString(posicion.obtenerPosEnX()));
		element.setAttribute("posEnY",Integer.toString(posicion.obtenerPosEnY()));

		return element;

	}
	
	private Element serializarObjetoConVida(Document doc,ObjetoConVida objeto){
		
		String tipo = "objetoConVida";
		Element elementoObjConVida = doc.createElement("objetoConVida");
		
		if(objeto instanceof CentralEolica){
			elementoObjConVida = doc.createElement("CentralEolica");
			tipo = "CentralEolica";
			if(((CentralElectrica)objeto).estaConectadaAlAgua())
				elementoObjConVida.setAttribute("conectadaAlAgua", "true");
			else elementoObjConVida.setAttribute("conectadaAlAgua", "false");
		}
		if(objeto instanceof CentralMineral){
			elementoObjConVida = doc.createElement("CentralMineral");
			tipo = "CentralMineral";
			if(((CentralElectrica)objeto).estaConectadaAlAgua())
				elementoObjConVida.setAttribute("conectadaAlAgua", "true");
			else elementoObjConVida.setAttribute("conectadaAlAgua", "false");
		}
		if(objeto instanceof CentralNuclear){
			elementoObjConVida = doc.createElement("CentralNuclear");
			tipo = "CentralNuclear";
			if(((CentralElectrica)objeto).estaConectadaAlAgua())
				elementoObjConVida.setAttribute("conectadaAlAgua", "true");
			else elementoObjConVida.setAttribute("conectadaAlAgua", "false");
		}
		if(objeto instanceof EstacionDeBomberos){
			elementoObjConVida = doc.createElement("EstacionDeBomberos");
			tipo = "EstacionDeBomberos";		
		}
		if(objeto instanceof LineaDeTension){
			elementoObjConVida = doc.createElement("LineaDeTension");
			tipo = "LineaDeTension";
			elementoObjConVida.appendChild(serializarObjetoConVida(doc,((LineaDeTension)objeto).getCentralElectrica()));
			elementoObjConVida.appendChild(serializarObjetoConVida(doc,((LineaDeTension)objeto).getZona()));
		}
		if(objeto instanceof PozoDeAgua){
			elementoObjConVida = doc.createElement("PozoDeAgua");
			tipo = "PozoDeAgua";
		}
		if(objeto instanceof RutaPavimentada){
			elementoObjConVida = doc.createElement("RutaPavimentada");
			tipo = "RutaPavimentada";
			elementoObjConVida.appendChild(serializarObjetoConVida(doc,((RutaPavimentada)objeto).getZona1()));
			elementoObjConVida.appendChild(serializarObjetoConVida(doc,((RutaPavimentada)objeto).getZona2()));
		}
		if(objeto instanceof TuberiaDeAgua){
			elementoObjConVida = doc.createElement("TuberiaDeAgua");
			tipo = "TuberiaDeAgua";
			elementoObjConVida.appendChild(serializarObjetoConVida(doc,((TuberiaDeAgua)objeto).getPozoDeAgua()));
			elementoObjConVida.appendChild(serializarObjetoConVida(doc,(Zona)((TuberiaDeAgua)objeto).getConectable()));
		}
		if(objeto instanceof Residencial){
			elementoObjConVida = doc.createElement("Residencial");
			tipo = "Residencial";
			if(((Zona)objeto).disponibilidadCloacas())
				elementoObjConVida.setAttribute("disponeCloacas", "true");
			else elementoObjConVida.setAttribute("disponeCloacas", "false");
			if(((Zona)objeto).disponibilidadTransito())
				elementoObjConVida.setAttribute("disponeAccesoTransito", "true");
			else elementoObjConVida.setAttribute("disponeAccesoTransito", "false");
			if(((Zona)objeto).disponibilidadElectrica())
				elementoObjConVida.setAttribute("disponeElectricidad", "true");
			else elementoObjConVida.setAttribute("disponeElectricidad", "false");
		}
		if(objeto instanceof Industrial){
			elementoObjConVida = doc.createElement("Industrial");
			tipo = "Industrial";
			if(((Zona)objeto).disponibilidadCloacas())
				elementoObjConVida.setAttribute("disponeCloacas", "true");
			else elementoObjConVida.setAttribute("disponeCloacas", "false");
			if(((Zona)objeto).disponibilidadTransito())
				elementoObjConVida.setAttribute("disponeAccesoTransito", "true");
			else elementoObjConVida.setAttribute("disponeAccesoTransito", "false");
			if(((Zona)objeto).disponibilidadElectrica())
				elementoObjConVida.setAttribute("disponeElectricidad", "true");
			else elementoObjConVida.setAttribute("disponeElectricidad", "false");
		}
		if(objeto instanceof Comercial){
			elementoObjConVida = doc.createElement("Comercial");
			tipo = "Comercial";
			if(((Zona)objeto).disponibilidadCloacas())
				elementoObjConVida.setAttribute("disponeCloacas", "true");
			else elementoObjConVida.setAttribute("disponeCloacas", "false");
			if(((Zona)objeto).disponibilidadTransito())
				elementoObjConVida.setAttribute("disponeAccesoTransito", "true");
			else elementoObjConVida.setAttribute("disponeAccesoTransito", "false");
			if(((Zona)objeto).disponibilidadElectrica())
				elementoObjConVida.setAttribute("disponeElectricidad", "true");
			else elementoObjConVida.setAttribute("disponeElectricidad", "false");
		}
		
		elementoObjConVida.setAttribute("tipo", tipo);
		elementoObjConVida.setAttribute("vida", Integer.toString(objeto.obtenerVida()));
		elementoObjConVida.appendChild(serializarPosicion(doc,objeto.obtenerPosicion()));

		return elementoObjConVida;
	}
	
	private Element serializarTerrenoDeAgua(Document doc,TerrenoDeAgua tAgua){
		
		Element element = doc.createElement("terrenoDeAgua");
		element.appendChild(serializarPosicion(doc,tAgua.getPosicion()));
		return element;
		
	}
	
	private Element serializarTerrenoLLano(Document doc,TerrenoLlano tLlano){
		
		Element element = doc.createElement("terrenoLlano");
		element.appendChild(serializarPosicion(doc,tLlano.getPosicion()));
		return element;

	}
	
	private Element serializarMapa(Document doc,Mapa mapa){
		
		Element elementoMapa = doc.createElement("mapa");
		elementoMapa.setAttribute("ancho",Integer.toString(mapa.getAncho()));
		elementoMapa.setAttribute("largo",Integer.toString(mapa.getLargo()));
		Element elementoObjetosConVida = doc.createElement("objetosConVida");
		elementoMapa.appendChild(elementoObjetosConVida);
		for (ObjetoConVida objeto : mapa.obtenerObjetosConVida()) {
			elementoObjetosConVida.appendChild(serializarObjetoConVida(doc,objeto));
		}
		Element elementoEstaciones = doc.createElement("estacionesDeBomberos");
		elementoMapa.appendChild(elementoEstaciones);
		for (EstacionDeBomberos estacion : mapa.obtenerEstaciones()) {
			elementoEstaciones.appendChild(serializarObjetoConVida(doc,estacion));
		}
		Element elementoTerrenosDeAgua = doc.createElement("terrenosDeAgua");
		elementoMapa.appendChild(elementoTerrenosDeAgua);
		for (TerrenoDeAgua tAgua : mapa.obtenerTerrenosDeAgua()) {
			elementoTerrenosDeAgua.appendChild(serializarTerrenoDeAgua(doc,tAgua));
		}
		Element elementoTerrenosLlano = doc.createElement("terrenosLlanos");
		elementoMapa.appendChild(elementoTerrenosLlano);
		for (TerrenoLlano tLlano : mapa.obtenerTerrenosLlanos()) {
			elementoTerrenosLlano.appendChild(serializarTerrenoLLano(doc,tLlano));
		}
		
		return elementoMapa;
	}
	
	private Element serializarJugador(Document doc,Jugador jugador){
		
		Element elementoJugador = doc.createElement("jugador");
		elementoJugador.setAttribute("dinero",Integer.toString(jugador.getDinero()));
		elementoJugador.setAttribute("nombre", jugador.getNombre());
		elementoJugador.appendChild(serializarMapa(doc,jugador.getMapa()));

		return elementoJugador;

	}
	
	
	private Element serializarJuego(Document doc,Juego juego){
		
		Element elementoJuego = doc.createElement("juego");
		elementoJuego.setAttribute("dias",Integer.toString(juego.getDias()));
		elementoJuego.setAttribute("cantResidencial",Integer.toString(juego.getCantResidencial()));
		elementoJuego.setAttribute("cantIndustrial",Integer.toString(juego.getCantIndustrial()));
		elementoJuego.appendChild(serializarJugador(doc,juego.obtenerJugador()));
		
		return elementoJuego;

	}
	
	
	
	//DESERIALIZAR*********************************************************
	private Posicion deserializarPosicion(Node elementoPosicion){
		
		int posX,posY;
		
		posX = (Integer.parseInt(((Element)elementoPosicion).getAttribute("posEnX")));
		posY = (Integer.parseInt(((Element)elementoPosicion).getAttribute("posEnY")));
		Posicion nuevaPosicion = new Posicion(posX,posY);
		return nuevaPosicion;

	}
	
	private TerrenoLlano deserializarTerrenoLlano(Node elementoLlano,Document doc){
		
		Posicion posicion = deserializarPosicion(elementoLlano.getChildNodes().item(0));
		
		TerrenoLlano nuevoTerrenoLlano = new TerrenoLlano(posicion);
		
		return nuevoTerrenoLlano;
	}
	
	private TerrenoDeAgua deserializarTerrenoDeAgua(Node elementoDeAgua,Document doc){
		
		Posicion posicion = deserializarPosicion(elementoDeAgua.getChildNodes().item(0));
		
		TerrenoDeAgua nuevoTerrenoDeAgua = new TerrenoDeAgua(posicion);
		
		return nuevoTerrenoDeAgua;
	}
	
	
	private ObjetoConVida deserializarObjetoConVida(Node elementoConVida,Document doc){
		
		ObjetoConVida nuevoObjeto;
		CentralElectrica central;
		Zona zona,zona1,zona2;
		PozoDeAgua pozo;
		ConectableATuberia conectable;
		String conectadaAlAgua,disponeElectricidad,disponeCloacas,disponeAccesoTransito;

		int vida = (Integer.parseInt(((Element)elementoConVida).getAttribute("vida")));
		Posicion posicion = new Posicion(0,0);
		nuevoObjeto = new PozoDeAgua(posicion);
		
		String tipo = ((Element)elementoConVida).getAttribute("tipo");
		
		if(tipo.equals("CentralEolica")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new CentralEolica(posicion);
			conectadaAlAgua = ((Element)elementoConVida).getAttribute("conectadaAlAgua");
			if(conectadaAlAgua.equals("true"))
				((CentralElectrica)nuevoObjeto).conectarAgua();
		}
		if(tipo.equals("CentralMineral")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new CentralMineral(posicion);
			conectadaAlAgua = ((Element)elementoConVida).getAttribute("conectadaAlAgua");
			if(conectadaAlAgua.equals("true"))
				((CentralElectrica)nuevoObjeto).conectarAgua();
		}
		if(tipo.equals("CentralNuclear")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new CentralNuclear(posicion);
			conectadaAlAgua = ((Element)elementoConVida).getAttribute("conectadaAlAgua");
			if(conectadaAlAgua.equals("true"))
				((CentralElectrica)nuevoObjeto).conectarAgua();
		}
		if(tipo.equals("EstacionDeBomberos")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new EstacionDeBomberos(posicion);
		}
		if(tipo.equals("LineaDeTension")){
			central = (CentralElectrica)deserializarObjetoConVida(elementoConVida.getChildNodes().item(0),doc);
			zona = 	(Zona)deserializarObjetoConVida(elementoConVida.getChildNodes().item(1),doc);
			nuevoObjeto = new LineaDeTension(central,zona);
		}
		if(tipo.equals("PozoDeAgua")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new PozoDeAgua(posicion);
		}
		if(tipo.equals("RutaPavimentada")){
			zona1 = (Zona)deserializarObjetoConVida(elementoConVida.getChildNodes().item(0),doc);
			zona2 = (Zona)deserializarObjetoConVida(elementoConVida.getChildNodes().item(1),doc);
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(2));
			nuevoObjeto = new RutaPavimentada(posicion);
			((RutaPavimentada)nuevoObjeto).conectarZonas(zona1,zona2);
		}
		if(tipo.equals("TuberiaDeAgua")){
			pozo = (PozoDeAgua)deserializarObjetoConVida(elementoConVida.getChildNodes().item(0),doc);
			conectable = (ConectableATuberia)deserializarObjetoConVida(elementoConVida.getChildNodes().item(1),doc);
			nuevoObjeto = new TuberiaDeAgua(pozo,(ObjetoConVida) conectable);
		}
		//if Zona
		if(tipo.equals("Comercial")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new Comercial(posicion);
			disponeCloacas = ((Element)elementoConVida).getAttribute("disponeCloacas");
			if(disponeCloacas.equals("true"))
				((Zona)nuevoObjeto).conectarAgua();
			disponeAccesoTransito = ((Element)elementoConVida).getAttribute("disponeAccesoTransito");
			if(disponeAccesoTransito.equals("true"))
				((Zona)nuevoObjeto).setDisponeAccesoTransito(true);
			disponeElectricidad = ((Element)elementoConVida).getAttribute("disponeElectricidad");
			if(disponeElectricidad.equals("true"))
				((Zona)nuevoObjeto).setDisponeElectricidad(true);
		}
		if(tipo.equals("Residencial")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new Residencial(posicion);
			disponeCloacas = ((Element)elementoConVida).getAttribute("disponeCloacas");
			if(disponeCloacas.equals("true"))
				((Zona)nuevoObjeto).conectarAgua();
			disponeAccesoTransito = ((Element)elementoConVida).getAttribute("disponeAccesoTransito");
			if(disponeAccesoTransito.equals("true"))
				((Zona)nuevoObjeto).setDisponeAccesoTransito(true);
			disponeElectricidad = ((Element)elementoConVida).getAttribute("disponeElectricidad");
			if(disponeElectricidad.equals("true"))
				((Zona)nuevoObjeto).setDisponeElectricidad(true);
		}
		if(tipo.equals("Industrial")){
			posicion = deserializarPosicion(elementoConVida.getChildNodes().item(0));
			nuevoObjeto = new Industrial(posicion);
			disponeCloacas = ((Element)elementoConVida).getAttribute("disponeCloacas");
			if(disponeCloacas.equals("true"))
				((Zona)nuevoObjeto).conectarAgua();
			disponeAccesoTransito = ((Element)elementoConVida).getAttribute("disponeAccesoTransito");
			if(disponeAccesoTransito.equals("true"))
				((Zona)nuevoObjeto).setDisponeAccesoTransito(true);
			disponeElectricidad = ((Element)elementoConVida).getAttribute("disponeElectricidad");
			if(disponeElectricidad.equals("true"))
				((Zona)nuevoObjeto).setDisponeElectricidad(true);
		}
		
		nuevoObjeto.setVida(vida);

		return nuevoObjeto;
		
	}
	
	private Mapa deserializarMapa(Node elementoMapa,Document doc){
		
		int largo,ancho;
		largo = (Integer.parseInt(((Element)elementoMapa).getAttribute("largo")));
		ancho = (Integer.parseInt(((Element)elementoMapa).getAttribute("ancho")));
		Mapa nuevoMapa = new Mapa(largo,ancho);
		
		Element elementosConVida = (Element)doc.getElementsByTagName("objetosConVida").item(0);
		for(int i=0; i< elementosConVida.getChildNodes().getLength(); i++) {
			ObjetoConVida objeto = deserializarObjetoConVida(elementosConVida.getChildNodes().item(i),doc);
			nuevoMapa.agregarObjeto(objeto);
		}
		Element elementosTAgua = (Element)doc.getElementsByTagName("terrenosDeAgua").item(0);
		for(int i=0; i< elementosTAgua.getChildNodes().getLength(); i++) {
			TerrenoDeAgua tDeAgua = deserializarTerrenoDeAgua(elementosTAgua.getChildNodes().item(i),doc);
			nuevoMapa.agregarTerrenoDeAgua(tDeAgua);
		}
		Element elementosTLlano = (Element)doc.getElementsByTagName("terrenosLlanos").item(0);
		for(int i=0; i< elementosTLlano.getChildNodes().getLength(); i++) {
			TerrenoLlano TLlano = deserializarTerrenoLlano(elementosTLlano.getChildNodes().item(i),doc);
			nuevoMapa.agregarTerrenoLlano(TLlano);
		}
		return nuevoMapa;
	}
	
	
	private Jugador deserializarJugador(Node elementoJugador,Document doc){
		
		int dinero;
		String nombre;
		
		nombre = (((Element)elementoJugador).getAttribute("nombre"));
		dinero = (Integer.parseInt(((Element)elementoJugador).getAttribute("dinero")));
		Element elementoMapa = (Element)doc.getElementsByTagName("jugador").item(0);

		Mapa mapa = deserializarMapa(elementoMapa.getChildNodes().item(0),doc);

		Jugador nuevoJugador = new Jugador(dinero,nombre,mapa);

		return nuevoJugador;

	}
	
	private Juego deserializarJuego(Document doc){
		
		int dias,cantResidencial,cantIndustrial;
		
		Element elementoJuego = (Element)doc.getElementsByTagName("juego").item(0);
		dias = (Integer.parseInt(((Element)elementoJuego).getAttribute("dias")));
		cantResidencial = (Integer.parseInt(((Element)elementoJuego).getAttribute("cantResidencial")));
		cantIndustrial = (Integer.parseInt(((Element)elementoJuego).getAttribute("cantIndustrial")));

		Jugador jugador = deserializarJugador(elementoJuego.getChildNodes().item(0),doc);
		Juego nuevoJuego = new Juego(jugador);
		nuevoJuego.setDias(dias);
		nuevoJuego.setCantResidencial(cantResidencial);
		nuevoJuego.setCantIndustrial(cantIndustrial);
		
		return nuevoJuego;
	}
	
	
	//guarda un juego en formato XML
	public void guardar(Juego juego) throws ParserConfigurationException, TransformerException, SAXException, IOException {
		
		//serializado del juego con todo su contenido
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.newDocument();

		Element juegoSerializado = serializarJuego(doc,juego);
		
		//--------------------------------------------
		
		// baja a disco
		
		doc.appendChild(juegoSerializado);
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		DOMSource source = new DOMSource(doc);
		File archivoDestino = new File(this.nombreArchivo);
		StreamResult result = new StreamResult(archivoDestino);
		transformer.transform(source, result);
	}
	
	
	//levanta un archivo XML y lo carga en un juego el cual devuelve 
	public Juego cargar() throws ParserConfigurationException, TransformerException, SAXException, IOException {

		File archivo = new File(this.nombreArchivo);

		// levanta el archivo de disco 
		//  y verifica que los objetos se deserializan correctamente

		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		Document doc = dBuilder.newDocument();
		doc = dBuilder.parse(archivo);
		doc.getDocumentElement().normalize();
		
		return deserializarJuego(doc);
		
	}


}
